test

Pre-Loading External Images in Gatsby JS

1 April, 2020 | 4 min read

Gatsby is a great tool that lets you build awesome applications! In fact I use Gatsby for my own site (hopefully you're reading this on there 😃), but I ran into an issue with loading externally hosted images. The issue was that since the images were externally hosted I had to fetch them on each page load and some images were optimized and thus it would take longer and affect the user experience.

I heard great things about the Gatsby Image library specifically for solving image optimization issues but didn't know how to use it for externally hosted images since my blog is powered by Ghost CMS. Luckily I managed to figure it out so this is a guide to walk you through what I did.

Installing Dependencies ⚙️

You're only going to need 3 dependencies, most of which you should already have, but just in case you don't, you're going to need the following:

  • gatsby-source-filesystem -> We're going to use a function this provides to download files from a remote source, in this case images. Import the createRemoteFileNode function from this in your file.
  • gatsby-plugin-sharp -> This will be used to generate the image object which is then ingested by gatsby-image. Import the fluid function from this in your file.
  • gatsby-image -> This provides the Img component that will be used in our React code

Implementation 👨‍💻

Most of the work will be done in the gatsby-node.js file, you can choose to do this in the createPages or createNode exported functions, I did it in the createPages just because I was running into some issues with my pagination plugin when attempting to do this in the createNode.

The createPages function is passed a param which has multiple functions, if destructring the param make sure to destructure the following. You can also further destructure from the actions param:

exports.createPages = async ({ actions, getCache, createNodeId, cache, reporter }) => {
const { createPage, createNode } = actions; 
... 
copied

I'm going to assume that you already have the URL(s) for the image(s) stored in some variable that you will pass to a function that will generate all of these images for you.

Firstly you'll have to create a fileNode which is essentially downloading the file (image) from a remore url and storing that as an object. That would look like this:

const fileNode = await createRemoteFileNode({
  url: image_url,
  parentNodeId: id,
  getCache,
  createNode,
  createNodeId,
});
copied

Now that you have "downloaded" the remote file (image) you'll now need to generate an image object from it using gatsby-plugin-sharp. That would look like this:

const generatedImage = await fluid({
  file: fileNode,
  reporter,
  cache,
});
copied

Now you have your generated image, and you can pass that to whatever page you want. Below you'll find a snippet from my implementation, and you'll see how that is passed to my posts pages. I'm using a map to store each generated image and to prevent duplicates, plus it makes retrieval of each image object easier for me. I then only pass the generated image for each specific post.

// This is the function that does all the work
const generateImages = async (pages) => {
  const featureImages = new Map();

  for (const page of pages) {
    const { node } = page;

    if (featureImages.has(node.slug)) {
      return;
    }

    const fileNode = await createRemoteFileNode({
      url: node.image_url,
      parentNodeId: node.id,
      getCache,
      createNode,
      createNodeId,
    });

    const generatedImage = await fluid({
      file: fileNode,
      reporter,
      cache,
    });

    featureImages.set(node.slug, generatedImage);
  }

  return featureImages;
};

const fluidImages = await generateImages(posts);

// Create post pages
posts.forEach(({ node }) => {
  createPage({
    path: node.url,
    component: postTemplate,
    context: {
      slug: node.slug,
      fluidImage: fluidImages.get(node.slug),
    },
  });
});
copied

So now that we have the gatsby-plugin-sharp fluid images and have them passed to the postTemplate page the usage is quite simple. Note that the fluidImage is accessible via the props specfically under props.pageContext.fluidImage.

Firstly you want to import the following at the top of your postTemplate or whatever component page.

import Img from 'gatsby-image';
copied

Now in your JSX simple use the Img component and access the passed fluidImage, it would look something like the following:

<Img fluid={props.pageContext.fluidImage} />
copied

You're all done!! 🎉🎉🎉

Yay you're all done, now that you know how to generate the image and use it, you can now go back and read the docs for the various libraries to figure out how to configure them for optimal performance for your use case!

Live Long and Program 🖖👨‍💻